// Copyright 2016-2017 Federico Bond <federicobond@gmail.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
// Copied from https://solidity.readthedocs.io/en/latest/grammar.html

// $antlr-format alignTrailingComments true, columnLimit 150, minEmptyLines 1, maxEmptyLinesToKeep 1, reflowComments false, useTab false
// $antlr-format allowShortRulesOnASingleLine false, allowShortBlocksOnASingleLine true, alignSemicolons hanging, alignColons hanging

grammar Solidity;

sourceUnit
    : (pragmaDirective | importDirective | structDefinition | enumDefinition | contractDefinition)* EOF
    ;

pragmaDirective
    : 'pragma' pragmaName pragmaValue ';'
    ;

pragmaName
    : identifier
    ;

pragmaValue
    : version
    | expression
    ;

version
    : versionConstraint versionConstraint?
    ;

versionConstraint
    : versionOperator? VersionLiteral
    ;

versionOperator
    : '^'
    | '~'
    | '>='
    | '>'
    | '<'
    | '<='
    | '='
    ;

importDirective
    : 'import' StringLiteralFragment ('as' identifier)? ';'
    | 'import' ('*' | identifier) ('as' identifier)? 'from' StringLiteralFragment ';'
    | 'import' '{' importDeclaration (',' importDeclaration)* '}' 'from' StringLiteralFragment ';'
    ;

importDeclaration
    : identifier ('as' identifier)?
    ;

contractDefinition
    : 'abstract'? ('contract' | 'interface' | 'library') identifier (
        'is' inheritanceSpecifier (',' inheritanceSpecifier)*
    )? '{' contractPart* '}'
    ;

inheritanceSpecifier
    : userDefinedTypeName ('(' expressionList? ')')?
    ;

contractPart
    : stateVariableDeclaration
    | usingForDeclaration
    | structDefinition
    | modifierDefinition
    | functionDefinition
    | eventDefinition
    | enumDefinition
    ;

stateVariableDeclaration
    : typeName (
        PublicKeyword
        | InternalKeyword
        | PrivateKeyword
        | ConstantKeyword
        | ImmutableKeyword
        | overrideSpecifier
    )* identifier ('=' expression)? ';'
    ;

overrideSpecifier
    : 'override' ('(' userDefinedTypeName (',' userDefinedTypeName)* ')')?
    ;

usingForDeclaration
    : 'using' identifier 'for' ('*' | typeName) ';'
    ;

structDefinition
    : 'struct' identifier '{' (variableDeclaration ';' (variableDeclaration ';')*)? '}'
    ;

modifierDefinition
    : 'modifier' identifier parameterList? (VirtualKeyword | overrideSpecifier)* (';' | block)
    ;

functionDefinition
    : functionDescriptor parameterList modifierList returnParameters? (';' | block)
    ;

functionDescriptor
    : 'function' (identifier | ReceiveKeyword | FallbackKeyword)?
    | ConstructorKeyword
    | FallbackKeyword
    | ReceiveKeyword
    ;

returnParameters
    : 'returns' parameterList
    ;

modifierList
    : (
        modifierInvocation
        | stateMutability
        | ExternalKeyword
        | PublicKeyword
        | InternalKeyword
        | PrivateKeyword
        | VirtualKeyword
        | overrideSpecifier
    )*
    ;

modifierInvocation
    : identifier ('(' expressionList? ')')?
    ;

eventDefinition
    : 'event' identifier eventParameterList AnonymousKeyword? ';'
    ;

enumDefinition
    : 'enum' identifier '{' enumValue? (',' enumValue)* '}'
    ;

enumValue
    : identifier
    ;

parameterList
    : '(' (parameter (',' parameter)*)? ')'
    ;

parameter
    : typeName storageLocation? identifier?
    ;

eventParameterList
    : '(' (eventParameter (',' eventParameter)*)? ')'
    ;

eventParameter
    : typeName IndexedKeyword? identifier?
    ;

variableDeclaration
    : typeName storageLocation? identifier
    ;

typeName
    : elementaryTypeName
    | userDefinedTypeName
    | mapping
    | typeName '[' expression? ']'
    | functionTypeName
    ;

userDefinedTypeName
    : identifier ('.' identifier)*
    ;

mapping
    : 'mapping' '(' (elementaryTypeName | userDefinedTypeName) '=>' typeName ')'
    ;

functionTypeName
    : 'function' parameterList modifierList returnParameters?
    ;

storageLocation
    : 'memory'
    | 'storage'
    | 'calldata'
    ;

stateMutability
    : PureKeyword
    | ConstantKeyword
    | ViewKeyword
    | PayableKeyword
    ;

block
    : '{' statement* '}'
    ;

statement
    : ifStatement
    | tryStatement
    | whileStatement
    | forStatement
    | block
    | inlineAssemblyStatement
    | doWhileStatement
    | continueStatement
    | breakStatement
    | returnStatement
    | throwStatement
    | emitStatement
    | simpleStatement
    ;

expressionStatement
    : expression ';'
    ;

ifStatement
    : 'if' '(' expression ')' statement ('else' statement)?
    ;

tryStatement
    : 'try' expression returnParameters? block catchClause+
    ;

// In reality catch clauses still are not processed as below
// the identifier can only be a set string: "Error". But plans
// of the Solidity team include possible expansion so we'll
// leave this as is, befitting with the Solidity docs.
catchClause
    : 'catch' (identifier? parameterList)? block
    ;

whileStatement
    : 'while' '(' expression ')' statement
    ;

forStatement
    : 'for' '(' (simpleStatement | ';') (expressionStatement | ';') expression? ')' statement
    ;

simpleStatement
    : (variableDeclarationStatement | expressionStatement)
    ;

inlineAssemblyStatement
    : 'assembly' StringLiteralFragment? assemblyBlock
    ;

doWhileStatement
    : 'do' statement 'while' '(' expression ')' ';'
    ;

continueStatement
    : 'continue' ';'
    ;

breakStatement
    : 'break' ';'
    ;

returnStatement
    : 'return' expression? ';'
    ;

// throw is no longer supported by latest Solidity.
throwStatement
    : 'throw' ';'
    ;

emitStatement
    : 'emit' functionCall ';'
    ;

// 'var' is no longer supported by latest Solidity.
variableDeclarationStatement
    : ('var' identifierList | variableDeclaration | '(' variableDeclarationList ')') (
        '=' expression
    )? ';'
    ;

variableDeclarationList
    : variableDeclaration? (',' variableDeclaration?)*
    ;

identifierList
    : '(' (identifier? ',')* identifier? ')'
    ;

elementaryTypeName
    : 'address' PayableKeyword?
    | 'bool'
    | 'string'
    | 'var'
    | Int
    | Uint
    | 'byte'
    | Byte
    | Fixed
    | Ufixed
    ;

Int
    : 'int'
    | 'int8'
    | 'int16'
    | 'int24'
    | 'int32'
    | 'int40'
    | 'int48'
    | 'int56'
    | 'int64'
    | 'int72'
    | 'int80'
    | 'int88'
    | 'int96'
    | 'int104'
    | 'int112'
    | 'int120'
    | 'int128'
    | 'int136'
    | 'int144'
    | 'int152'
    | 'int160'
    | 'int168'
    | 'int176'
    | 'int184'
    | 'int192'
    | 'int200'
    | 'int208'
    | 'int216'
    | 'int224'
    | 'int232'
    | 'int240'
    | 'int248'
    | 'int256'
    ;

Uint
    : 'uint'
    | 'uint8'
    | 'uint16'
    | 'uint24'
    | 'uint32'
    | 'uint40'
    | 'uint48'
    | 'uint56'
    | 'uint64'
    | 'uint72'
    | 'uint80'
    | 'uint88'
    | 'uint96'
    | 'uint104'
    | 'uint112'
    | 'uint120'
    | 'uint128'
    | 'uint136'
    | 'uint144'
    | 'uint152'
    | 'uint160'
    | 'uint168'
    | 'uint176'
    | 'uint184'
    | 'uint192'
    | 'uint200'
    | 'uint208'
    | 'uint216'
    | 'uint224'
    | 'uint232'
    | 'uint240'
    | 'uint248'
    | 'uint256'
    ;

Byte
    : 'bytes'
    | 'bytes1'
    | 'bytes2'
    | 'bytes3'
    | 'bytes4'
    | 'bytes5'
    | 'bytes6'
    | 'bytes7'
    | 'bytes8'
    | 'bytes9'
    | 'bytes10'
    | 'bytes11'
    | 'bytes12'
    | 'bytes13'
    | 'bytes14'
    | 'bytes15'
    | 'bytes16'
    | 'bytes17'
    | 'bytes18'
    | 'bytes19'
    | 'bytes20'
    | 'bytes21'
    | 'bytes22'
    | 'bytes23'
    | 'bytes24'
    | 'bytes25'
    | 'bytes26'
    | 'bytes27'
    | 'bytes28'
    | 'bytes29'
    | 'bytes30'
    | 'bytes31'
    | 'bytes32'
    ;

Fixed
    : 'fixed'
    | ( 'fixed' [0-9]+ 'x' [0-9]+)
    ;

Ufixed
    : 'ufixed'
    | ( 'ufixed' [0-9]+ 'x' [0-9]+)
    ;

expression
    : expression ('++' | '--')
    | 'new' typeName
    | expression '[' expression? ']'
    | expression '[' expression? ':' expression? ']'
    | expression '.' identifier
    | expression '{' nameValueList '}'
    | expression '(' functionCallArguments ')'
    | PayableKeyword '(' expression ')'
    | '(' expression ')'
    | ('++' | '--') expression
    | ('+' | '-') expression
    | ('after' | 'delete') expression
    | '!' expression
    | '~' expression
    | expression '**' expression
    | expression ('*' | '/' | '%') expression
    | expression ('+' | '-') expression
    | expression ('<<' | '>>') expression
    | expression '&' expression
    | expression '^' expression
    | expression '|' expression
    | expression ('<' | '>' | '<=' | '>=') expression
    | expression ('==' | '!=') expression
    | expression '&&' expression
    | expression '||' expression
    | expression '?' expression ':' expression
    | expression ('=' | '|=' | '^=' | '&=' | '<<=' | '>>=' | '+=' | '-=' | '*=' | '/=' | '%=') expression
    | primaryExpression
    ;

primaryExpression
    : BooleanLiteral
    | numberLiteral
    | hexLiteral
    | stringLiteral
    | identifier ('[' ']')?
    | TypeKeyword
    | tupleExpression
    | typeNameExpression ('[' ']')?
    ;

expressionList
    : expression (',' expression)*
    ;

nameValueList
    : nameValue (',' nameValue)* ','?
    ;

nameValue
    : identifier ':' expression
    ;

functionCallArguments
    : '{' nameValueList? '}'
    | expressionList?
    ;

functionCall
    : expression '(' functionCallArguments ')'
    ;

tupleExpression
    : '(' (expression? ( ',' expression?)*) ')'
    | '[' ( expression ( ',' expression)*)? ']'
    ;

typeNameExpression
    : elementaryTypeName
    | userDefinedTypeName
    ;

assemblyItem
    : identifier
    | assemblyBlock
    | assemblyExpression
    | assemblyLocalDefinition
    | assemblyAssignment
    | assemblyStackAssignment
    | labelDefinition
    | assemblySwitch
    | assemblyFunctionDefinition
    | assemblyFor
    | assemblyIf
    | BreakKeyword
    | ContinueKeyword
    | LeaveKeyword
    | subAssembly
    | numberLiteral
    | stringLiteral
    | hexLiteral
    ;

assemblyBlock
    : '{' assemblyItem* '}'
    ;

assemblyExpression
    : assemblyCall
    | assemblyLiteral
    ;

assemblyCall
    : ('return' | 'address' | 'byte' | identifier) (
        '(' assemblyExpression? ( ',' assemblyExpression)* ')'
    )?
    ;

assemblyLocalDefinition
    : 'let' assemblyIdentifierList (':=' assemblyExpression)?
    ;

assemblyAssignment
    : assemblyIdentifierList ':=' assemblyExpression
    ;

assemblyIdentifierList
    : identifier (',' identifier)*
    ;

assemblyStackAssignment
    : '=:' identifier
    ;

labelDefinition
    : identifier ':'
    ;

assemblySwitch
    : 'switch' assemblyExpression assemblyCase*
    ;

assemblyCase
    : 'case' assemblyLiteral assemblyType? assemblyBlock
    | 'default' assemblyBlock
    ;

assemblyFunctionDefinition
    : 'function' identifier '(' assemblyTypedVariableList? ')' assemblyFunctionReturns? assemblyBlock
    ;

assemblyFunctionReturns
    : ('-' '>' assemblyTypedVariableList)
    ;

assemblyFor
    : 'for' assemblyBlock assemblyExpression assemblyBlock assemblyBlock
    ;

assemblyIf
    : 'if' assemblyExpression assemblyBlock
    ;

assemblyLiteral
    : (stringLiteral | DecimalNumber | HexNumber | hexLiteral | BooleanLiteral) assemblyType?
    ;

assemblyTypedVariableList
    : identifier assemblyType? (',' assemblyTypedVariableList)?
    ;

assemblyType
    : ':' identifier
    ;

subAssembly
    : 'assembly' identifier assemblyBlock
    ;

numberLiteral
    : (DecimalNumber | HexNumber) NumberUnit?
    ;

identifier
    : ('from' | 'calldata' | 'address' | Identifier)
    ;

BooleanLiteral
    : 'true'
    | 'false'
    ;

DecimalNumber
    : (DecimalDigits | (DecimalDigits? '.' DecimalDigits)) ([eE] '-'? DecimalDigits)?
    ;

fragment DecimalDigits
    : [0-9] ('_'? [0-9])*
    ;

HexNumber
    : '0' [xX] HexDigits
    ;

fragment HexDigits
    : HexCharacter ('_'? HexCharacter)*
    ;

NumberUnit
    : 'wei'
    | 'szabo'
    | 'finney'
    | 'ether'
    | 'seconds'
    | 'minutes'
    | 'hours'
    | 'days'
    | 'weeks'
    | 'years'
    ;

HexLiteralFragment
    : 'hex' (('"' HexDigits? '"') | ('\'' HexDigits? '\''))
    ;

hexLiteral
    : HexLiteralFragment+
    ;

fragment HexPair
    : HexCharacter HexCharacter
    ;

fragment HexCharacter
    : [0-9A-Fa-f]
    ;

ReservedKeyword
    : 'after'
    | 'case'
    | 'default'
    | 'final'
    | 'in'
    | 'inline'
    | 'let'
    | 'match'
    | 'null'
    | 'of'
    | 'relocatable'
    | 'static'
    | 'switch'
    | 'typeof'
    ;

AnonymousKeyword
    : 'anonymous'
    ;

BreakKeyword
    : 'break'
    ;

ConstantKeyword
    : 'constant'
    ;

ImmutableKeyword
    : 'immutable'
    ;

ContinueKeyword
    : 'continue'
    ;

LeaveKeyword
    : 'leave'
    ;

ExternalKeyword
    : 'external'
    ;

IndexedKeyword
    : 'indexed'
    ;

InternalKeyword
    : 'internal'
    ;

PayableKeyword
    : 'payable'
    ;

PrivateKeyword
    : 'private'
    ;

PublicKeyword
    : 'public'
    ;

VirtualKeyword
    : 'virtual'
    ;

PureKeyword
    : 'pure'
    ;

TypeKeyword
    : 'type'
    ;

ViewKeyword
    : 'view'
    ;

ConstructorKeyword
    : 'constructor'
    ;

FallbackKeyword
    : 'fallback'
    ;

ReceiveKeyword
    : 'receive'
    ;

Identifier
    : IdentifierStart IdentifierPart*
    ;

fragment IdentifierStart
    : [a-zA-Z$_]
    ;

fragment IdentifierPart
    : [a-zA-Z0-9$_]
    ;

stringLiteral
    : StringLiteralFragment+
    ;

StringLiteralFragment
    : '"' DoubleQuotedStringCharacter* '"'
    | '\'' SingleQuotedStringCharacter* '\''
    ;

fragment DoubleQuotedStringCharacter
    : ~["\r\n\\]
    | ('\\' .)
    ;

fragment SingleQuotedStringCharacter
    : ~['\r\n\\]
    | ('\\' .)
    ;

VersionLiteral
    : [0-9]+ '.' [0-9]+ ('.' [0-9]+)?
    ;

WS
    : [ \t\r\n\u000C]+ -> skip
    ;

COMMENT
    : '/*' .*? '*/' -> channel(HIDDEN)
    ;

LINE_COMMENT
    : '//' ~[\r\n]* -> channel(HIDDEN)
    ;